/*  Copyright (C) 2008
*
*
*  This program is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; by version 2 of the License.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*
*
*/

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
#include <bluetooth/sdp.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <bluetooth/sdp_lib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <X11/Xlib.h>
#include "nokchuklib.h"
#include "x11_event.h"


int nokchuk_do_action(nokchuk *session){
	int ac = 0 ,x ,y ;
	ac = nokchuk_get_action(session);
	if( ac == MOVE ){
		x = session->action->x;
		//apply offset
		y = session->action->y - session->control->offset;
		
		//TODO : smooth mouse move
		if (x > session->control->level1 || x < - session->control->level1){
			mouse_move(x/MOVE_CONST,0,session->display);
		}
		if (y > session->control->level1 || y < - session->control->level1){
			mouse_move(0,y/MOVE_CONST * session->control->inv,session->display);
		}
		return MOVE;
	}
	else if ( ac == KEY_PRESS ){
		if (strcmp(session->action->key,"Key0") == 0)
			send_event(SPACE,session->display);
		else if (strcmp(session->action->key,"Up") == 0)
			send_event(ARROW_UP,session->display);
		else if (strcmp(session->action->key,"Down") == 0)
			send_event(ARROW_DOWN,session->display);
		else if (strcmp(session->action->key,"Left") == 0)
			send_event(ARROW_LEFT,session->display);
		else if (strcmp(session->action->key,"Right") == 0)
			send_event(ARROW_RIGHT,session->display);
		else if (strcmp(session->action->key,"Enter") == 0)
			send_event(ENTER,session->display);
		else if (strcmp(session->action->key,"Backspace") == 0)
			send_event(DEL,session->display);
		else if (strcmp(session->action->key,"Key3") == 0)
			send_event(F5,session->display);
		else if (strcmp(session->action->key,"Key1") == 0)
			send_event(ESC,session->display);
		else if (strcmp(session->action->key,"Key7") == 0)
			send_event(KEY_P,session->display);
		else if (strcmp(session->action->key,"Key9") == 0)
			send_event(KEY_N,session->display);
		else if (strcmp(session->action->key,"Star") == 0)
			send_event(KEY_F,session->display);
		else if (strcmp(session->action->key,"Hash") == 0)
			send_event(KEY_T,session->display);
		//handling mouse clicks
		else if (strcmp(session->action->key,"Key4") == 0){
			mouse_click(MOUSE_BUTTON_LEFT,MOUSE_BUTTON_PRESS,session->display);
			mouse_click(MOUSE_BUTTON_LEFT,MOUSE_BUTTON_RELEASE,session->display);
		}
		//element selection
		else if (strcmp(session->action->key,"Edit") == 0){
			if (session->control->btn1){
				mouse_click(MOUSE_BUTTON_LEFT,MOUSE_BUTTON_RELEASE,session->display);
				session->control->btn1 = 0;
			}else{
				mouse_click(MOUSE_BUTTON_LEFT,MOUSE_BUTTON_PRESS,session->display);
				session->control->btn1 = 1;
			}
		}
		else if (strcmp(session->action->key,"Key5") == 0){
			mouse_click(MOUSE_BUTTON_MIDDLE,MOUSE_BUTTON_PRESS,session->display);
			mouse_click(MOUSE_BUTTON_MIDDLE,MOUSE_BUTTON_RELEASE,session->display);
		}
		else if (strcmp(session->action->key,"Key6") == 0){
			mouse_click(MOUSE_BUTTON_RIGHT,MOUSE_BUTTON_PRESS,session->display);
			mouse_click(MOUSE_BUTTON_RIGHT,MOUSE_BUTTON_RELEASE,session->display);
		}
		else if (strcmp(session->action->key,"Key2") == 0){
			mouse_click(MOUSE_SCROLL_UP,MOUSE_BUTTON_PRESS,session->display);
			mouse_click(MOUSE_SCROLL_UP,MOUSE_BUTTON_RELEASE,session->display);
		}
		else if (strcmp(session->action->key,"Key8") == 0){
			mouse_click(MOUSE_SCROLL_DOWN,MOUSE_BUTTON_PRESS,session->display);
			mouse_click(MOUSE_SCROLL_DOWN,MOUSE_BUTTON_RELEASE,session->display);
		}
		return KEY_PRESS;
	}
	return END;
}


int nokchuk_get_coord(char* data,nokchuk *session){
	session->action->type = MOVE;
	char* tmp = NULL;
	tmp = (char*)strtok(data,",");
	if(tmp != NULL)
		session->action->x = atoi(tmp);
	else
		return -1;
	tmp = (char*)strtok(NULL,",");
	if(tmp != NULL)
		session->action->y = atoi(tmp);
	else
		return -1;
	tmp = (char*)strtok(NULL,",");
	if(tmp != NULL)
		session->action->z = atoi(tmp);
	else
		return -1;
	return 1;
}


/*
*	return KEY_PRESS if a key was read , MOVE if the phone send coordinates
*	and END if the phone terminated the connection
*/
int nokchuk_get_action(nokchuk *session){
	char *line = NULL;
	int i = 0;
	line = nokchuk_read(session);
	//as coordinates are separated by coma , search for a coma in the read line , a bit nasty...
	if (strchr(line, ',') != NULL){
		i = nokchuk_get_coord(line,session);
		if(i){
			session->action->type = MOVE;
			return MOVE;
		}else{
			session->action->type = NONE;
			return NONE;
		}
	}
	else{
		if (strcmp(line,"end") == 0){
			session->action->type = END;
			return END;
		}else{
			session->action->type = KEY_PRESS;
			session->action->key = line;
			return KEY_PRESS;
		}
	}		
}

//read a line from socket
char* nokchuk_read(nokchuk *session){
	char buf[MAX_BUFFER];
	int bytes_read = 0;
    memset(buf, 0, sizeof(buf));
	bytes_read = read(session->socket_client, buf, sizeof(buf));
    if( bytes_read > 0 ) {
		return strdup(buf);
    }
	return strdup("");	
}

int nokchuk_client_socket(nokchuk *session){
	char buf[64];
	struct sockaddr_rc rem_addr = { 0 };
	socklen_t opt = sizeof(rem_addr);

	printf("Waiting for an incoming connection\n");
	// accept one connection
	session->socket_client = accept(session->socket, (struct sockaddr *)&rem_addr, &opt);
	if(session->socket_client < 1 )
		return -1;
	ba2str( &rem_addr.rc_bdaddr, buf );
	fprintf(stderr, "Accepted connection from %s\n", buf);
	//updating mac adress into session
	session->client_mac = strdup(buf);
	return 1;
}


int nokchuk_bluetooth_socket(nokchuk *session){
	struct sockaddr_rc loc_addr = { 0 };

	// allocate socket
	session->socket = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
	// local bluetooth adapter
	loc_addr.rc_family = AF_BLUETOOTH;
	loc_addr.rc_bdaddr = *BDADDR_ANY;
	loc_addr.rc_channel = (uint8_t) session->channel;
	bind(session->socket, (struct sockaddr *)&loc_addr, sizeof(loc_addr));
	if(session->socket < 1 )
		return -1;
	// put socket into listening mode
	listen(session->socket, 1);
	printf("Server Started\n");
	return 1;
}

/* 	Original code from Amora project
 *
 * This code is derived from Albert Huang description of
 * how to describe/register a service withing SDP.
 * Copyright (C) 2005 Albert Huang
 */
int nokchuk_describe_service( nokchuk *sn)
{
	uint32_t service_uuid_int[] = { 0, 0, 0, 0xAFFF };
	uint8_t rfcomm_channel = sn->channel;
	const char *service_name = strdup("Nokchuk");
	const char *service_dsc = strdup("Nokchuk");
	const char *service_prov = strdup("Nokchuk");

	uuid_t root_uuid, l2cap_uuid, rfcomm_uuid, svc_uuid;
	sdp_list_t *l2cap_list = 0, *rfcomm_list = 0, *root_list = 0,
		*proto_list = 0, *access_proto_list = 0,  *svclass_id = 0,
		*profiles = 0;

	sdp_data_t *channel = 0;
	sdp_record_t *record = sdp_record_alloc();

	int err = -1;
	sdp_session_t *session = 0;
	sdp_profile_desc_t profile;

	/* set the general service ID */
	sdp_uuid128_create(&svc_uuid, &service_uuid_int);
	sdp_set_service_id(record, svc_uuid);

	/* make the service record publicly browsable */
	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
	root_list = sdp_list_append(0, &root_uuid);
	sdp_set_browse_groups(record, root_list);

	/* Set port attributes, this description came from sdptool.c (from
	 * bluez-utils/tools).
	 * Copyright (C) 2001-2002  Nokia Corporation
	 * Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
	 * Copyright (C) 2002-2007  Marcel Holtmann <marcel@holtmann.org>
	 * Copyright (C) 2002-2003  Stephen Crane <steve.crane@rococosoft.com>
	 * Copyright (C) 2002-2003  Jean Tourrilhes <jt@hpl.hp.com>
	 *
	 */
	sdp_uuid16_create(&root_uuid, SERIAL_PORT_SVCLASS_ID);
	svclass_id = sdp_list_append(0, &root_uuid);
	sdp_set_service_classes(record, svclass_id);

	sdp_uuid16_create(&profile.uuid, SERIAL_PORT_PROFILE_ID);
	profile.version = 0x0100;
	profiles = sdp_list_append(0, &profile);
	sdp_set_profile_descs(record, profiles);


	/* set l2cap information */
	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
	l2cap_list = sdp_list_append(0, &l2cap_uuid);
	proto_list = sdp_list_append(0, l2cap_list);

	/* set rfcomm information */
	sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
	channel = sdp_data_alloc(SDP_UINT8, &rfcomm_channel);
	rfcomm_list = sdp_list_append(0, &rfcomm_uuid);
	sdp_list_append(rfcomm_list, channel);
	sdp_list_append(proto_list, rfcomm_list);

	/* attach protocol information to service record */
	access_proto_list = sdp_list_append(0, proto_list);
	sdp_set_access_protos(record, access_proto_list);

	/* set the name, provider, and description */
	sdp_set_info_attr(record, service_name, service_prov, service_dsc);

	/* connect to the local SDP server, register the service record, and
	 * disconnect
	 */
	session = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, SDP_RETRY_IF_BUSY);
	if (!session) {
		perror("describe_service: cannot connect to bluetooth"
			    " device. Is bluetooth daemon running?\n");
		goto exit;
	}

	err = sdp_record_register(session, record, 0);
	if (err == -1) {
		perror("describe_service: error registering service!");
	}

exit:
	/* cleanup */
	sdp_data_free(channel);
	sdp_list_free(l2cap_list, 0);
	sdp_list_free(rfcomm_list, 0);
	sdp_list_free(root_list, 0);
	sdp_list_free(access_proto_list, 0);
	sdp_list_free(svclass_id, 0);
	sdp_list_free(profiles, 0);
	sn->sdp_session = (void*) session;
	sn->sdp_record = (void*) record;

	return err;
}

nokchuk* nokchuk_init(int channel){
	int ret = 0;
	//int screen = 0;
	//init nokchuk struct
	nokchuk *conn = NULL;
	nokchuk_control *mov = NULL;
	
	conn = malloc(sizeof(nokchuk));
	if(conn == NULL){
		perror("Unable to allocate memory");
		exit(-1);
	}
	conn->channel = channel;
	conn->socket = 0;
	conn->socket_client = 0;
	conn->client_mac = NULL;
	conn->sdp_record = NULL;
	conn->sdp_session = NULL;
	conn->action = NULL;
	conn->action = malloc(sizeof(nokchuk_action));
	if(conn->action == NULL){
		perror("Unable to allocate memory");
		exit(-1);
	}
	//type of action ( KEY_PRESS , MOVE , END , NONE)
	conn->action->type = NONE;
	conn->action->key = strdup("");
	//coordinates are updates each time the server receive phone's coordinates
	conn->action->x = 0;
	conn->action->y = 0;
	conn->action->z = 0;
	//display to use
	conn->display = NULL;
	mov = malloc(sizeof(nokchuk_control));
	if(mov == NULL){
		perror("Unable to allocate memory");
		exit(-1);
	}
	//movement relative vars
	//Y offset to avoid breaking cervicals by watching phone
	mov->offset = OFFSET;
	//Y should be inverted to be more natural
	mov->inv = -1;
	mov->btn1 = 0;
	mov->level1 = LEVEL1;
	//pass pointer to main struct
	conn->control = mov;
	//try to register rfcomm service with sdp
	ret = nokchuk_describe_service(conn);
	if (ret < 0 ){
		perror("Error : unable to register the service");
		exit(-1);
	}
	//try to open display
	conn->display = construct_display(NULL);
	if ( conn->display == NULL ){
		perror("Error : Unable to create X11 display");
		exit(-1);
	}
	//FIXME : try to be more screen-specifically
	
	/*screen = DefaultScreen(conn->display);
	printf("Width::%d\tHeight::%d\n",
                                DisplayWidth(conn->display,screen),
                                DisplayHeight(conn->display,screen));*/

	//return the initialized struct
	return conn;
}

int nokchuk_start(nokchuk *session){
	int ret;
	//starting welcome socket
	ret = nokchuk_bluetooth_socket(session);
	if ( ret < 0){
		printf("Error : Unable to launch the server\n");
		exit(-1);
	}
	//waiting for a client to connect on the socket
	ret = nokchuk_client_socket(session);
	if ( ret < 0){
		printf("Error : Unable to create client socket\n");
		exit(-1);
	}
	return 1;
}

//stop nokchuk properly
int nokchuk_stop(nokchuk *session){
	close(session->socket);
	close(session->socket_client);
	destroy_display(session->display);
	sdp_record_unregister((sdp_session_t *)session->sdp_session,(sdp_record_t *)session->sdp_record);
	sdp_close(session->sdp_session);
	free(session->action);
	free(session);
	return 1;
}
